package cn.com.zzl.util;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * Description：网络编程http工具类
 * Created by 朱正磊 on 2017-04-14 15:32.
 */
public class HttpUtil {

    //字符编码格式为UTF-8，在使用doPostHttp方法的时候会使用到。
    public static final String UTF_8 = "UTF-8";

    private static CloseableHttpClient httpClient = null;

    private final static Logger logger = LoggerFactory.getLogger(HttpUtil.class);

    //创建连接池管理器，它管理着连接池，可以同时为很多线程提供http连接请求。
    private final static PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();

    //HTTP保留时间
    private final static int MAX_HTTP_KEEP_ALIVE = 30 * 1000;

    //最大连接数
    private final static int MAX_TOTAL_CONNECTIONS = 800;

    //每个路由最大连接数
    private final static int MAX_ROUTE_CONNECTIONS = 400;

    //获取连接超时时间
    private final static int CONNECT_TIMEOUT = 5000;

    //连接处理超时时间
    private final static int SOCKET_TIMEOUT = 20000;

    //静态代码块，随着类的加载而执行，并且只执行一次
    static {

        //customRetryHandler()是本类的私有方法
        HttpRequestRetryHandler myRetryHandler = customRetryHandler();

        //customKeepAliveHander()是本类的私有方法
        ConnectionKeepAliveStrategy customKeepAliveHander = customKeepAliveHander();

        //连接池管理器设置最大连接数
        connManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);

        //连接池管理器设置每个路由最大连接数
        connManager.setDefaultMaxPerRoute(MAX_ROUTE_CONNECTIONS);

        httpClient = HttpClients.custom()
                                .setConnectionManager(connManager)
                                //使用接口ConnectionKeepAliveStrategy的实现类对象
                                .setKeepAliveStrategy(customKeepAliveHander)
                                //使用接口HttpRequestRetryHandler的实现类对象
                                .setRetryHandler(myRetryHandler)
                                .build();
    }

    /**
     * 说明：doPostHttp的应用场景：

            String content = JSON.toJSONString(dataMap);//json格式的字符串，其实就是调用接口需要的参数
            //一般我们将接口的访问地址即URL配置在配置文件里面，
            //比如：app.gather.url=http://test.miaodj.cn/index.php/App/ApiCapital/payCapital
            String url = PropertiesUtil.payCapital();
            String charset = HttpUtil.UTF_8;//字符编码UTF-8
            ContentType contentType = ContentType.APPLICATION_JSON;//内容格式
            //resultContent就是我们获取的接口数据，说明：接口数据的格式一般也是json格式的字符串
            //我们一般都会将json字符串转换成json对象进行操作json数据！JSON.parseObject(resultContent);
            String resultContent = HttpUtil.doPostHttp(url,content,charset,contentType);

            在ContentType最终类中提供了内容格式：
            public static final ContentType APPLICATION_JSON;
            APPLICATION_JSON = create("application/json", Consts.UTF_8);

     * @Desc 以post方式请求指定的http服务
     * @param url http url，说明：请求访问的URL地址
     *            比如：http://test.miaodj.cn/index.php/App/ApiCapital/payCapital
     * @param content 请求体，说明：该内容是json格式的字符串！
     * @param charset 字符编码(如UTF-8)
     * @param contentType 内容格式(如application/json)
     * @return 返回响应结果
     */
    public static String doPostHttp(String url, String content, String charset
                                              , ContentType contentType) {
        logger.info("post请求地址：" + url + " , 请求参数：" + content);
        RequestConfig requestConfig = RequestConfig.custom()
                                                    //设置请求连接超时时间
                                                   .setConnectTimeout(CONNECT_TIMEOUT)
                                                    //设置请求处理超时时间
                                                   .setSocketTimeout(SOCKET_TIMEOUT)
                                                   .build();
        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(requestConfig);
        StringEntity reqEntity = new StringEntity(content, contentType);
        httpPost.setEntity(reqEntity);
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != HttpStatus.SC_OK) {
                logger.warn("请求失败:statusCode={0}", statusCode);
                HttpEntity repEntity = response.getEntity();
                if (repEntity != null) {
                    logger.warn("请求响应:resp={0}", EntityUtils.toString(repEntity, charset));
                }
                return "";
            }
            HttpEntity repEntity = response.getEntity();
            String resp = EntityUtils.toString(repEntity, charset);
            return resp;
        }catch (IOException e){
            e.printStackTrace();
            logger.error("请求失败",e);
            return "请求失败：" + e;
        }finally {
            HttpClientUtils.closeQuietly(response);
        }
    }

    /**
     * @Desc 以post方式请求指定的http服务（默认就是使用UTF-8字符编码和application/json内容格式）
     * @param url 请求的URL地址
     * @param content 请求的内容参数
     * @return
     */
    public static String doPostHttp(String url, String content) {
        return doPostHttp(url, content, UTF_8, ContentType.APPLICATION_JSON);
    }

    /**
     * 说明：doGetHttp的应用场景：
     * @Desc 以get方式请求指定的http服务
     * @param url http url
     * @param charset 字符编码格式，如UTF-8
     * @return 返回响应结果
     */
    public static String doGetHttp(String url, String charset) {
        logger.info("get请求地址: " + url);
        RequestConfig requestConfig = RequestConfig.custom()
                                                    //设置请求连接超时时间
                                                   .setConnectTimeout(CONNECT_TIMEOUT)
                                                    //设置请求处理超时时间
                                                   .setSocketTimeout(SOCKET_TIMEOUT)
                                                   .build();
        HttpGet httpGet = new HttpGet(url);
        httpGet.setConfig(requestConfig);
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpGet);
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                logger.warn("请求失败");
                return "";
            }
            HttpEntity entity = response.getEntity();
            String resp = EntityUtils.toString(entity, charset);
            logger.info("请求响应:{0}", resp);
            return resp;
        } catch (IOException e) {
            e.printStackTrace();
            logger.error("请求失败",e);
            return "请求失败：" + e;
        }
        finally {
            HttpClientUtils.closeQuietly(response);
        }
    }

    /**
     * @Desc 以get方式请求指定的http服务（默认就是使用UTF-8字符编码）
     * @param url 请求访问的URL地址
     * @return
     */
    public static String doGetHttp(String url) {
        return doGetHttp(url, UTF_8);
    }

    /**
     * 私有方法，仅供本类使用
     * @Desc 是否重试，说明：该方法使用了内部类，实现了接口中的retryRequest方法，方法内容就是返回false
     * @return 返回接口的实现类实例对象，只有一个方法retryRequest，该方法的返回值是false，我们定义false为不重试
     */
    private static HttpRequestRetryHandler customRetryHandler() {
        HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                return false;
            }
        };
        return myRetryHandler;
    }

    /**
     * 私有方法，仅供本类使用
     * @Desc 设置HTTP连接保留时间，该方法的实现形式跟customRetryHandler本质上是类似的
     * 使用内部类，实现接口的抽象方法，返回接口的实现类的实例化对象，方法的内容就是一句返回值
     * @return 保留策略
     */
    private static ConnectionKeepAliveStrategy customKeepAliveHander() {
        ConnectionKeepAliveStrategy myKeepAliveHander = new ConnectionKeepAliveStrategy() {
            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                return MAX_HTTP_KEEP_ALIVE;
            }
        };
        return myKeepAliveHander;
    }

    /**
     * @Desc 获取用户IP地址
     * @param request
     * @return
     */
    public static String getIpAddress(HttpServletRequest request){
        String ip = request.getHeader("X-Forwarded-For");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if(ip == null || ip.length() ==0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getRemoteAddr();
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = "";
        }
        if(ip != null && ip.indexOf(",") != -1 && ip.length() > 6){
            ip = ip.split(",")[0];
        }
        return ip;
    }

}
